View Javadoc

1   /*
2    * TouchGraph LLC. Apache-Style Software License
3    *
4    *
5    * Copyright (c) 2001-2002 Alexander Shapiro. All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer. 
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution,
20   *    if any, must include the following acknowledgment:  
21   *       "This product includes software developed by 
22   *        TouchGraph LLC (http://www.touchgraph.com/)."
23   *    Alternately, this acknowledgment may appear in the software itself,
24   *    if and wherever such third-party acknowledgments normally appear.
25   *
26   * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse 
27   *    or promote products derived from this software without prior written 
28   *    permission.  For written permission, please contact 
29   *    alex@touchgraph.com
30   *
31   * 5. Products derived from this software may not be called "TouchGraph",
32   *    nor may "TouchGraph" appear in their name, without prior written
33   *    permission of alex@touchgraph.com.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL TOUCHGRAPH OR ITS CONTRIBUTORS BE 
39   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
40   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
41   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
42   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
43   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
44   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
45   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46   * ====================================================================
47   *
48   */
49  
50  package com.touchgraph.graphlayout;
51  
52  import java.util.*;
53  
54  import com.touchgraph.graphlayout.graphelements.*;
55   
56  /*** LocalityUtils:  Utilities for switching locality.  Animation effects
57    * require a reference to TGPanel.
58    *     
59    * @author   Alexander Shapiro                                        
60    * @version  1.21  $Id: LocalityUtils.java,v 1.1.1.1 2004/02/06 08:44:05 keesj Exp $
61    */
62  public class LocalityUtils {
63  
64      TGPanel tgPanel;
65      Locality locality;
66      
67      public static final int INFINITE_LOCALITY_RADIUS = Integer.MAX_VALUE;
68      
69      ShiftLocaleThread shiftLocaleThread;
70      boolean fastFinishShift=false;  // If finish fast is true, quickly wrap up animation
71      
72      public LocalityUtils(Locality loc, TGPanel tgp) {
73          locality = loc;
74          tgPanel = tgp;
75      }
76  
77      public void fastFinishAnimation() {
78          fastFinishShift = true;
79      }
80  
81      /*** Mark for deletion nodes not contained within distHash. */
82      private synchronized boolean markDistantNodes(final Collection subgraph) {
83          final boolean[] someNodeWasMarked = new boolean[1]; 
84          someNodeWasMarked[0] = false;
85          Boolean x;
86          TGForEachNode fen = new TGForEachNode() {
87              public void forEachNode(Node n) {
88                  if(!subgraph.contains(n)) { 
89                      n.markedForRemoval=true;
90                      someNodeWasMarked[0] = true;
91                  }
92              }
93          };
94          
95          locality.forAllNodes(fen);
96          return someNodeWasMarked[0];
97      }
98  
99      private synchronized void removeMarkedNodes() {
100         final Vector nodesToRemove = new Vector();
101         
102         TGForEachNode fen = new TGForEachNode() {
103             public void forEachNode(Node n) {
104                 if(n.markedForRemoval)  {
105                     nodesToRemove.addElement(n);
106                     n.markedForRemoval=false;    
107                 }
108             }    
109         };
110         synchronized(locality) {    
111             locality.forAllNodes(fen);
112             locality.removeNodes(nodesToRemove);
113         }    
114     }
115 
116     /*** Add to locale nodes within radius distance of a focal node. */
117     private synchronized void addNearNodes(Hashtable distHash, int radius) throws TGException {
118         for ( int r=0; r<radius+1; r++ ) {
119             Enumeration localNodes = distHash.keys();
120             while (localNodes.hasMoreElements()) {
121                 Node n = (Node)localNodes.nextElement();
122                 if(!locality.contains(n) && ((Integer)distHash.get(n)).intValue()<=r) {
123                     n.justMadeLocal = true;
124                     locality.addNodeWithEdges(n);
125                     if (!fastFinishShift) {
126                         try { Thread.sleep(50); } 
127                         catch (InterruptedException ex) {}
128                     }
129                 }
130             }
131         }
132     }
133 
134     private synchronized void unmarkNewAdditions() {
135         TGForEachNode fen = new TGForEachNode() {
136             public void forEachNode(Node n) {
137                 n.justMadeLocal=false;
138             }    
139         };
140         locality.forAllNodes(fen);
141     }
142 
143     /*** The thread that gets instantiated for doing the locality shift animation. */
144     class ShiftLocaleThread extends Thread {
145         Hashtable distHash;
146         Node focusNode;        
147         int radius;
148         int maxAddEdgeCount;
149         int maxExpandEdgeCount;
150         boolean unidirectional;
151                 
152         ShiftLocaleThread(Node n, int r, int maec, int meec, boolean unid) {                                         
153             focusNode = n;                        
154             radius = r;             
155             maxAddEdgeCount = maec;
156             maxExpandEdgeCount = meec;
157             unidirectional = unid;
158             start();
159          
160         }
161             
162         public void run() {        
163             synchronized (LocalityUtils.this) {
164             //Make sure node hasn't been deleted
165                 if (!locality.getCompleteEltSet().contains(focusNode)) return;                        
166                 tgPanel.stopDamper();
167                 distHash = GESUtils.calculateDistances(
168                              locality.getCompleteEltSet(),focusNode,radius,maxAddEdgeCount,maxExpandEdgeCount,unidirectional);                         
169                 try {
170                     if (markDistantNodes(distHash.keySet())) {
171                          for (int i=0;i<5&&!fastFinishShift;i++) {
172                              Thread.sleep(100);                      
173                          }
174                     }
175                     removeMarkedNodes();
176                     for (int i=0;i<1&&!fastFinishShift;i++) {
177                         Thread.sleep(100); 
178                     }
179                     addNearNodes(distHash,radius);
180                     for (int i=0;i<4&&!fastFinishShift;i++) {
181                         Thread.sleep(100); 
182                     }
183                     unmarkNewAdditions();
184                 } catch ( TGException tge ) {
185                     System.err.println("TGException: " + tge.getMessage());
186                 } catch (InterruptedException ex) {}    
187                 tgPanel.resetDamper();            
188             }
189         }
190     }
191             
192     public void setLocale(Node n, final int radius, final int maxAddEdgeCount, final int maxExpandEdgeCount,
193                           final boolean unidirectional) throws TGException {                                  
194         if (n==null || radius<0) return;
195         if(shiftLocaleThread!=null && shiftLocaleThread.isAlive()) {
196             fastFinishShift=true; //This should cause last locale shift to finish quickly
197             while(shiftLocaleThread.isAlive())
198                 try { Thread.sleep(100); } 
199                 catch (InterruptedException ex) {}
200         }
201         if (radius == INFINITE_LOCALITY_RADIUS || n==null) {
202             addAllGraphElts();
203             tgPanel.resetDamper();
204             return;
205         }
206 
207         fastFinishShift=false;        
208         shiftLocaleThread=new ShiftLocaleThread(n, radius, maxAddEdgeCount, maxExpandEdgeCount, unidirectional);
209     }
210 
211     public void setLocale(Node n, final int radius) throws TGException {                                      
212         setLocale(n,radius,1000,1000, false);
213     }
214     
215     public synchronized void addAllGraphElts() throws TGException {
216         locality.addAll();
217     }
218     
219    /*** Add to locale nodes that are one edge away from a given node.
220      * This method does not utilize "fastFinishShift" so it's likely that 
221      * synchronization errors will occur.
222      */
223     public void expandNode(final Node n) {
224         new Thread() {
225             public void run() {
226                 synchronized (LocalityUtils.this) {
227                     if (!locality.getCompleteEltSet().contains(n)) return;                        
228                     tgPanel.stopDamper();
229                     for(int i=0;i<n.edgeCount();i++) {
230                         Node newNode = n.edgeAt(i).getOtherEndpt(n);
231                         if (!locality.contains(newNode)) {
232                             newNode.justMadeLocal = true;
233                             try {
234                                 locality.addNodeWithEdges(newNode);
235                                 Thread.sleep(50); 
236                             } catch ( TGException tge ) {
237                                 System.err.println("TGException: " + tge.getMessage());
238                             } catch ( InterruptedException ex ) {}         
239                         }
240                         else if (!locality.contains(n.edgeAt(i))) {
241                             locality.addEdge(n.edgeAt(i));
242                         }
243                     }
244                     try { Thread.sleep(200); } 
245                     catch (InterruptedException ex) {}         
246                     unmarkNewAdditions();
247                     tgPanel.resetDamper();
248                 }
249             }
250         }.start();
251     }
252     
253    /*** Hides a node, and all the nodes attached to it. */
254     public synchronized void hideNode( final Node hideNode ) {
255         if (hideNode==null) return;
256         new Thread() {
257             public void run() {
258                 synchronized(LocalityUtils.this) {
259                     if (!locality.getCompleteEltSet().contains(hideNode)) return;                                                               
260                     
261                     locality.removeNode(hideNode); //Necessary so that node is ignored in distances calculation.
262                     if (hideNode==tgPanel.getSelect()) {
263                         tgPanel.clearSelect();
264                     }
265                     
266                                            
267                     Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality);
268                     markDistantNodes(subgraph);                      
269                     tgPanel.repaint();
270                     try { Thread.sleep(200); } 
271                     catch (InterruptedException ex) {}         
272                     removeMarkedNodes();                    
273                     
274                     tgPanel.resetDamper();
275                 }
276             }
277         }.start();
278     }
279 
280    /*** Opposite of expand node, works like hide node except that the selected node is not hidden.*/
281     public synchronized void collapseNode( final Node collapseNode ) {
282         if (collapseNode==null) return;
283         new Thread() {
284             public void run() {
285                 synchronized(LocalityUtils.this) {
286                     if (!locality.getCompleteEltSet().contains(collapseNode)) return;                                                               
287                     
288                     locality.removeNode(collapseNode); //Necessary so that node is ignored in distances calculation.                                                               
289                     Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality);
290                     markDistantNodes(subgraph);                                          
291                     try {
292                         locality.addNodeWithEdges(collapseNode); // Add the collapsed node back in.
293                     } 
294                     catch (TGException tge) { tge.printStackTrace(); }
295                     tgPanel.repaint();
296                     try { Thread.sleep(200); } 
297                     catch (InterruptedException ex) {}         
298                     removeMarkedNodes();                    
299                     
300                     tgPanel.resetDamper();
301                 }
302             }
303         }.start();
304     }
305 } // end com.touchgraph.graphlayout.LocalityUtils